#include "StateChanged.hh"
#include "../World/Galaxy.hh"
#include "../World/User.hh"
#include "../Exception.hh"
#include "XmlClient.hh"

/**
 * \brief Hibaüzenet struct definíció.
 * \param NAME    A hibaüzenet struct neve.
 * \param MESSAGE A hibaüzenet szövege.
 *
 * A parse_str és parse_num függvényekben használható. Függvény szerűen meg
 * lehet hívni, ilyenkor NonFatalProtocolError kivételt dob, a megadott
 * üzenettel.
 */
#define ERROR(NAME, MESSAGE)                                            \
    struct NAME##Invalid {                                              \
        void operator() () {                                            \
            throw                                                       \
              NonFatalProtocolError(                                    \
                "game.state_changed." + QString((MESSAGE)) + " invalid" \
              );                                                        \
        }                                                               \
    }                                                                   \

using namespace Geom;
using namespace Network;
using namespace World;

struct TProductInvalid {
    void operator () () {
        throw NonFatalProtocolError(
          "game.state_changed.planets.planet.tasks.task.product invalid"
        );
    }
};

struct TAmountInvalid {
    void operator () () {
        throw NonFatalProtocolError(
          "game.state_changed.planets.planet.tasks.task.amount invalid"
        );
    }
};

StateChanged::TaskInfo::TaskInfo():
    product(),
    amount() {}

StateChanged::TaskInfo::TaskInfo(Planet::Task const& task):
    product(task.get_name()),
    amount(task.get_amount()) {}

StateChanged::TaskInfo::TaskInfo(QDomElement const& taskElem):
    product(),
    amount()
{
    QDomElement const productElem = taskElem.firstChildElement("product");
    QDomElement const amountElem = taskElem.firstChildElement("amount");
    product = ::parse_str< TProductInvalid    >(productElem);
    amount  = ::parse_num< u8, TAmountInvalid >(amountElem);
}

void StateChanged::TaskInfo::to_xml(QDomNode& parent) const {
    QDomDocument doc = parent.ownerDocument();
    QDomElement taskElem = doc.createElement("task");
    parent.appendChild(taskElem);
    ::to_xml(taskElem, "product", product);
    ::to_xml(taskElem, "amount", amount);
}

ERROR(PId,         "planets.planet.id");
ERROR(PSize,       "planets.planet.size");
ERROR(POwner,      "planets.planet.owner");
ERROR(PPop,        "planets.planet.population");
ERROR(PFood,       "planets.planet.food");
ERROR(PDwelling,   "planets.planet.dwelling");
ERROR(PFactory,    "planets.planet.factory");
ERROR(PFarm,       "planets.planet.farm");
ERROR(PReactor,    "planets.planet.reactor");
ERROR(PFighter,    "planets.planet.fighter");
ERROR(PTank,       "planets.planet.tank");

StateChanged::PlanetInfo::PlanetInfo():
    id(),
    size(),
    owner(),
    population(),
    food(),
    dwelling(),
    factory(),
    farm(),
    reactor(),
    fighter(),
    tank() {}

StateChanged::PlanetInfo::PlanetInfo(Planet const& planet):
    id(planet.id),
    size(planet.get_size()),
    owner(),
    tasks(),
    population(planet.get_population()),
    food(planet.get_food()),
    dwelling(planet.get_dwelling()),
    factory(planet.get_factory()),
    farm(planet.get_farm()),
    reactor(planet.get_reactor()),
    fighter(planet.get_fighter()),
    tank(planet.get_tank())
{
    spPlayer player = planet.get_owner();
    owner = player ? player->get_name() : "";
    QVector< Planet::spTask > _tasks = planet.get_tasks();

    for (
      QVector< Planet::spTask >::const_iterator task = _tasks.begin();
      task != _tasks.end();
      ++task
    ) {
        TaskInfo ti(**task);
        tasks.append(ti);
    }
}

/**
 * \brief Bolygó információk kinyerése XML-ből.
 * \param planetElem Az XML element, amiben a bolygó adatai vannak.
 */
StateChanged::PlanetInfo::PlanetInfo(QDomElement const& planetElem):
    id(),
    size(),
    owner(),
    population(),
    food(),
    dwelling(),
    factory(),
    farm(),
    reactor()
{
    QDomElement const idElem = planetElem.firstChildElement("id");
    QDomElement const sizeElem = planetElem.firstChildElement("size");
    QDomElement const ownerElem = planetElem.firstChildElement("owner");
    QDomElement const tasksElem = planetElem.firstChildElement("tasks");
    QDomElement taskElem = tasksElem.firstChildElement("task");

    while (!taskElem.isNull()) {
        TaskInfo task(taskElem);
        tasks.append(task);
        taskElem = taskElem.nextSiblingElement("task");
    }

    QDomElement const populationElem =
      planetElem.firstChildElement("population");
    QDomElement const foodElem = planetElem.firstChildElement("food");
    QDomElement const dwellingElem = planetElem.firstChildElement("dwelling");
    QDomElement const factoryElem = planetElem.firstChildElement("factory");
    QDomElement const farmElem = planetElem.firstChildElement("farm");
    QDomElement const reactorElem = planetElem.firstChildElement("reactor");
    QDomElement const fighterElem = planetElem.firstChildElement("fighter");
    QDomElement const tankElem = planetElem.firstChildElement("tank");
    id         = parse_num< u8, PIdInvalid       >(idElem);
    size       = parse_num< u8, PSizeInvalid     >(sizeElem);
    owner      = parse_str< POwnerInvalid        >(ownerElem,      "");
    population = parse_num< u8, PPopInvalid      >(populationElem, 0);
    food       = parse_num< u8, PFoodInvalid     >(foodElem,       0);
    dwelling   = parse_num< u8, PDwellingInvalid >(dwellingElem,   0);
    factory    = parse_num< u8, PFactoryInvalid  >(factoryElem,    0);
    farm       = parse_num< u8, PFarmInvalid     >(farmElem,       0);
    reactor    = parse_num< u8, PReactorInvalid  >(reactorElem,    0);
    fighter    = parse_num< u8, PFighterInvalid  >(fighterElem,    0);
    tank       = parse_num< u8, PTankInvalid     >(tankElem,       0);
}

/**
 * \brief Bolygó információk XML-be írása.
 * \param parent Az XML node, amelyen belül létrehozza a planet element-et.
 *               Kimeneti paraméter.
 */
void StateChanged::PlanetInfo::to_xml(QDomNode& parent) const {
    QDomDocument doc = parent.ownerDocument();
    QDomElement planetElem = doc.createElement("planet");
    QDomElement tasksElem = doc.createElement("tasks");
    parent.appendChild(planetElem);
    parent.appendChild(tasksElem);
    ::to_xml(planetElem, "id",         id);
    ::to_xml(planetElem, "size",       size);
    ::to_xml(planetElem, "owner",      owner,      QString());

    for (
      QVector<TaskInfo>::const_iterator task = tasks.begin();
      task != tasks.end();
      ++task
    ) {
        task->to_xml(tasksElem);
    }

    ::to_xml(planetElem, "population", population, u8(0));
    ::to_xml(planetElem, "food",       food,       u8(0));
    ::to_xml(planetElem, "dwelling",   dwelling,   u8(0));
    ::to_xml(planetElem, "factory",    factory,    u8(0));
    ::to_xml(planetElem, "farm",       farm,       u8(0));
    ::to_xml(planetElem, "reactor",    reactor,    u8(0));
    ::to_xml(planetElem, "fighter",    fighter,    u8(0));
    ::to_xml(planetElem, "tank",       tank,       u8(0));
}

ERROR(FId,          "fleets.fleet.id");
ERROR(FOwner,       "fleets.fleet.owner");
ERROR(FBattle,      "fleets.fleet.battleship");
ERROR(FFighter,     "fleets.fleet.fighter");
ERROR(FColony,      "fleets.fleet.colonyship");
ERROR(FTransport,   "fleets.fleet.transportship");
ERROR(FTank,        "fleets.fleet.tank");

StateChanged::FleetInfo::FleetInfo(Fleet const& fleet):
    id(fleet.id),
    position(fleet.get_position()),
    battleship(fleet.get_battleship()),
    fighter(fleet.get_fighter()),
    colonyship(fleet.get_colonyship()),
    transportship(fleet.get_transportship()),
    tank(fleet.get_tank()) {}

/**
 * \brief Flotta információk kinyerése XML-ből.
 * \param fleetElem Az XML element, amiben a flotta adatai vannak.
 */
StateChanged::FleetInfo::FleetInfo(QDomElement const& fleetElem):
    id(),
    position(),
    battleship(),
    fighter(),
    colonyship(),
    transportship(),
    tank()
{
    QDomElement const idElem = fleetElem.firstChildElement("id");
    QDomElement const posElem = fleetElem.firstChildElement("pos");
    QDomElement const battleshipElem =
      fleetElem.firstChildElement("battleship");
    QDomElement const fighterElem = fleetElem.firstChildElement("fighter");
    QDomElement const colonyshipElem =
      fleetElem.firstChildElement("colonyship");
    QDomElement const transportshipElem =
      fleetElem.firstChildElement("transportship");
    QDomElement const tankElem = fleetElem.firstChildElement("tank");
    id = parse_num<u8, FIdInvalid >(idElem);

    if (!position.from_xml(posElem))
        throw
          NonFatalProtocolError("game.state_changed.fleets.fleet.pos invalid");

    battleship    = parse_num<u8, FBattleInvalid    >(battleshipElem,    0);
    fighter       = parse_num<u8, FFighterInvalid   >(fighterElem,       0);
    colonyship    = parse_num<u8, FColonyInvalid    >(colonyshipElem,    0);
    transportship = parse_num<u8, FTransportInvalid >(transportshipElem, 0);
    tank          = parse_num<u8, FTankInvalid      >(tankElem,          0);
}

/**
 * \brief Flotta információk XML-be írása.
 * \param parent Az XML node, amelyen belül létrehozza a fleet element-et.
 *               Kimeneti paraméter.
 */
void StateChanged::FleetInfo::to_xml(QDomNode& parent) const {
    QDomDocument doc = parent.ownerDocument();
    QDomElement fleetElem = doc.createElement("fleet");
    parent.appendChild(fleetElem);
    ::to_xml(fleetElem, "id",            id);
    if (!position.to_xml(fleetElem, "pos"))
        throw;
    ::to_xml(fleetElem, "battleship",    battleship,     u8(0));
    ::to_xml(fleetElem, "fighter",       fighter,        u8(0));
    ::to_xml(fleetElem, "colonyship",    colonyship,     u8(0));
    ::to_xml(fleetElem, "transportship", transportship,  u8(0));
    ::to_xml(fleetElem, "tank",          tank,           u8(0));
}

StateChanged::PlayerInfo::PlayerInfo():
    name(),
    money(),
    fleets() {}

StateChanged::PlayerInfo::PlayerInfo(Player const& player):
    name(player.get_name()),
    money(player.get_money()),
    fleets()
{
    World::FleetMap _fleets = player.get_fleets();

    for (
      World::FleetMap::iterator fleet = _fleets.begin();
      fleet != _fleets.end();
      ++fleet
    ) {
        fleets.insert(fleet.key(), *fleet.value());
    }
}

ERROR(PName,  "players.player.name");
ERROR(PMoney, "players.player.money");

StateChanged::PlayerInfo::PlayerInfo(QDomElement const& playerElem):
    name(),
    money(),
    fleets()
{
    QDomElement const nameElem = playerElem.firstChildElement("name");
    QDomElement const moneyElem = playerElem.firstChildElement("money");
    QDomElement const fleetsElem = playerElem.firstChildElement("fleets");
    QDomElement fleetElem = fleetsElem.firstChildElement("fleet");
    name = parse_str< PNameInvalid >(nameElem);
    money = parse_num< u8, PMoneyInvalid >(moneyElem);

    while (!fleetElem.isNull()) {
        FleetInfo fleet(fleetElem);
        fleets.insert(fleet.id, fleet);
        fleetElem = fleetElem.nextSiblingElement("fleet");
    }
}

void StateChanged::PlayerInfo::to_xml(QDomNode& parent) const {
    QDomDocument doc = parent.ownerDocument();
    QDomElement playerElem = doc.createElement("player");
    QDomElement fleetsElem = doc.createElement("fleets");
    parent.appendChild(playerElem);
    playerElem.appendChild(fleetsElem);
    ::to_xml(playerElem, "name",  name);
    ::to_xml(playerElem, "money", money);

    for (
      FleetMap::const_iterator fleet = fleets.begin();
      fleet != fleets.end();
      ++fleet
    ) {
        fleet->to_xml(fleetsElem);
    }
}

StateChanged::StarInfo::StarInfo():
    id(),
    position(),
    planets() {}

ERROR(SId,  "stars.star.id");
ERROR(SPos, "stars.star.pos");

StateChanged::StarInfo::StarInfo(Star const& star):
    id(star.id),
    position(star.position),
    planets()
{
    Star::PlanetMap _planets = star.get_planets();

    for (
      Star::PlanetMap::iterator planet = _planets.begin();
      planet != _planets.end();
      ++planet
    ) {
        planets.append(**planet);
    }
}

/**
 * \brief Csillag információk kinyerése XML-ből.
 * \param startElem Az XML element, amiben a csillag adatai vannak.
 */
StateChanged::StarInfo::StarInfo(QDomElement const& starElem):
    id(),
    position(),
    planets()
{
    QDomElement const idElem = starElem.firstChildElement("id");
    QDomElement const posElem = starElem.firstChildElement("pos");
    QDomElement const planetsElem = starElem.firstChildElement("planets");
    QDomElement planetElem = planetsElem.firstChildElement("planet");
    id       = parse_num   < u8, SIdInvalid  >(idElem);

    if (!position.from_xml(posElem))
        throw
          NonFatalProtocolError("game.state_changed.stars.star.pos invalid");

    while (!planetElem.isNull()) {
        PlanetInfo planet(planetElem);
        planets.insert(planet.id, planet);
        planetElem = planetElem.nextSiblingElement("planet");
    }
}

/**
 * \brief Csillag információk XML-be írása.
 * \param parent Az XML node, amelyen belül létrehozza a star element-et.
 *               Kimeneti paraméter.
 */
void StateChanged::StarInfo::to_xml(QDomNode& parent) const {
    QDomDocument doc = parent.ownerDocument();
    QDomElement starElem = doc.createElement("star");
    QDomElement planetsElem = doc.createElement("planets");
    parent.appendChild(starElem);
    starElem.appendChild(planetsElem);
    ::to_xml(starElem, "id",  id);
    position.to_xml(starElem, "pos");

    for (
      PlanetList::const_iterator planet = planets.begin();
      planet != planets.end();
      ++planet
    ) {
        planet->to_xml(planetsElem);
    }
}

StateChanged::GalaxyInfo::GalaxyInfo(Galaxy const& galaxy):
    players(),
    stars()
{
    PlayerMapByUser _players = galaxy.get_players();

    for (
      PlayerMapByUser::const_iterator player = _players.begin();
      player != _players.end();
      ++player
    ) {
        players.insert(player.key()->name, PlayerInfo(*player.value()));
    }

    World::StarMap _stars = galaxy.get_stars();

    for (
      Galaxy::StarMap::const_iterator star = _stars.begin();
      star != _stars.end();
      ++star
    ) {
        stars.insert(star.key(), StarInfo(*star.value()));
    }
}

/**
 * \brief Galaxis információk kinyerése XML-ből.
 * \param galaxyElem Az XML element, amiben a galaxis adatai vannak.
 */
StateChanged::GalaxyInfo::GalaxyInfo(QDomElement const& galaxyElem):
    players(),
    stars()
{
    QDomElement const playersElem = galaxyElem.firstChildElement("players");
    QDomElement const starsElem = galaxyElem.firstChildElement("stars");
    QDomElement playerElem = playersElem.firstChildElement("player");
    QDomElement starElem = starsElem.firstChildElement("star");

    while (!playerElem.isNull()) {
        PlayerInfo player(playerElem);
        players.insert(player.name, player);
        playerElem = playerElem.nextSiblingElement("player");
    }

    while (!starElem.isNull()) {
        StarInfo star(starElem);
        stars.insert(star.id, star);
        starElem = starElem.nextSiblingElement("star");
    }
}

/**
 * \brief Galaxis információk XML-be írása.
 * \param parent Az XML node, amelyen belül létrehozza a fleets és stars
 *               element-eket. Kimeneti paraméter.
 */
void StateChanged::GalaxyInfo::to_xml(QDomNode& parent) const {
    QDomDocument doc = parent.ownerDocument();
    QDomElement playersElem = doc.createElement("players");
    QDomElement starsElem = doc.createElement("stars");
    parent.appendChild(playersElem);
    parent.appendChild(starsElem);

    for (
      PlayerMap::const_iterator player = players.begin();
      player != players.end();
      ++player
    ) {
        player->to_xml(playersElem);
    }

    for (
      StarMap::const_iterator star = stars.begin();
      star != stars.end();
      ++star
    ) {
        star->to_xml(starsElem);
    }
}

spStateChanged StateChanged::construct(QDomElement const& message) {
    GalaxyInfo galaxy(message);
    return spStateChanged(new StateChanged(galaxy));
}

StateChanged::StateChanged(GalaxyInfo const& galaxy):
    galaxyInfo(galaxy) {}

StateChanged::StateChanged(Galaxy const& galaxy):
    galaxyInfo(galaxy) {}

QDomDocument StateChanged::to_xml() const {
    QDomDocument doc;
    QDomElement gameElem = doc.createElement("game");
    QDomElement stateChangedElem = doc.createElement("state_changed");
    doc.appendChild(gameElem);
    gameElem.appendChild(stateChangedElem);
    galaxyInfo.to_xml(stateChangedElem);
    return doc;
}

void StateChanged::process_on_client(XmlClient& client) {
	if (client.get_state() != NEXT_TURN_SENT)
		throw ProtocolError("game.state_changed unexpected");

	client.clear();

    for (
      GalaxyInfo::StarMap::const_iterator star = galaxyInfo.stars.begin();
      star != galaxyInfo.stars.end();
      ++star
    ) {
        client.show_star(star.value());
    }

	for (
	  GalaxyInfo::PlayerMap::const_iterator player = galaxyInfo.players.begin();
	  player != galaxyInfo.players.end();
	  ++player
	) {
		for (
		  PlayerInfo::FleetMap::const_iterator fleet = player->fleets.begin();
		  fleet != player->fleets.end();
		  ++fleet
		) {
			client.show_fleet(fleet.value(), player.key());
		}

	}
}

#undef ERROR
